package com.evola.conditionalformatting
{

	import flash.utils.Dictionary;
	import flash.utils.getTimer;

	import mx.collections.ICollectionView;

	/**
	 * Fasadna klasa za rad sa CF-om.
	 *
	 * Primeri izraza koji su validni za CF:
	 * goal#color:@name.|A$AND$@name|.S;goal#icon(name=smile)|color(uint=123):@dueDate(d)=%today$AND$@budget(n)<|1000
	 *
	 * @author Sasa
	 *
	 */
	public class ConditionalFormatting
	{

		private static var goalSets:Dictionary=new Dictionary();

		public static function createGoalSet(expression:String):ViewGoalsSet
		{

			return parseExpression(expression);
		}

		public static function getGoal(expression:String, goalType:String, value:Object, dataProvider:ICollectionView):ViewGoal
		{

			var viewGoals:ViewGoalsSet=getViewGoalSet(expression);

			var viewGoal:ViewGoal=null;

			if (viewGoals)
			{

				viewGoal=viewGoals.resolveGoalValue(goalType, value, dataProvider);
			}

			return viewGoal;
		}

		public static function getViewGoalSet(expression:String):ViewGoalsSet
		{

			if (!expression)
				return null;

			var expObject:ViewGoalsSet=goalSets[expression];

			if (!expObject)
			{

				expObject=parseExpression(expression);
				goalSets[expression]=expObject;
			}

			return expObject;
		}

		/**
		 * Metoda pravi objektni model izraza.
		 * Primeri izraza:
		 * goal#color(uint=123456):@Amount>50$OR$@Amount<30;goal#color(uint=AAEECC):@Amount<=50;goal#color(uint=FFEEDD):@Amount>=100$AND$@Type=1
		 * bgcolor#123456:@Amount>50$OR$@Amount<30;bgcolor#AAEECC:@Amount<=50;bgcolor#FFEEDD:@Amount>=100$AND$@Type=1
		 *
		 */
		private static function parseExpression(expressionString:String):ViewGoalsSet
		{

			var goals:Array=new Array();
			//prvo izraz rastavljamo sa znakom ';', taj znam nam je separator pojedinih elemenata tj. item-a
			var colorSubexps:Array=expressionString.split(";");

			//za svaki podizraz formiramo boju
			for each (var colorSubexp:String in colorSubexps)
			{

				//bice prazan string ako nam je znak ';' i na kraju izraza, to je ok
				if (colorSubexp == "")
					continue;

				if (colorSubexp.indexOf("goal#") != 0)
				{
					throw new Error("CF subexpression must begin with 'goal'.");
				}

				var goalString:String=colorSubexp.match(ViewGoal.REGEX)[0];

				if (!goalString)
				{
					throw new Error("CF subexpression's goal is not defined correctly. It sould be in the following format: 'goal#type(params):'.");
				}

				//sada sastavljamo goal
				var goal:ViewGoal=parseGoal(goalString);

				//trazimo znak ':', to nam je separator izmedju boje i izraza za uslov
				var colonIndex:int=colorSubexp.indexOf(":");

				//ostatak izraza nam je cist uslovni izraz
				goal.conditions=parseConditions(colorSubexp.substr(colonIndex + 1));

				goals.push(goal);
			}

			var viewColors:ViewGoalsSet=new ViewGoalsSet(expressionString, goals);
			return viewColors;
		}

		/**
		 * Metoda dobija izraz za cilj u obliku:
		 * #type(prop1=a,prop2=b):
		 * i za taj izraz pravi objekat ViewGoal.
		 * @param goalString
		 * @return
		 *
		 */
		private static function parseGoal(goalString:String):ViewGoal
		{

			//sada nam goalString sadrzi i znak # i : pa to uklanjamo
			goalString=goalString.substr(1, goalString.length - 2);

			var goal:ViewGoal=new ViewGoal();
			goal.goalsData=[];

			//rastavljamo sve ciljeve (ako ih ima vise)

			var goals:Array=goalString.split("|");

			for each (var goalDataString:String in goals)
			{

				var goalType:String = null;
				var params:Object = null;

				var bracketIndex:int=goalDataString.indexOf("(");

				//izraz ne mora da sadrzi parametre odnosno zagradu
				if (goalDataString.indexOf("(") != -1)
				{

					goalType=goalDataString.substring(0, bracketIndex);

					//imamo i parametre
					params=new Object();

					var paramsString:String=goalDataString.substring(bracketIndex + 1, goalDataString.length - 1);

					if (paramsString) //mozda imamo samo prazne zagrade, npr. goal#color():
					{
						var paramParts:Array=paramsString.split(",");

						for each (var paramPart:String in paramParts)
						{

							var arr:Array=paramPart.split("=");
							var paramName:String=arr[0];
							var paramValue:String=arr[1];

							params[paramName]=paramValue;
						}
					}
				}
				else
				{
					goalType=goalDataString;
				}

				var goalData:ViewGoalData=new ViewGoalData();

				goalData.type=goalType;
				goalData.properties=params;

				goal.goalsData.push(goalData);
			}

			return goal;
		}

		private static function parseConditions(conditionExpression:String):Array
		{

			//moze se desiti da nam je uslovni izraz prazan, to je bezuslovna boja ili default
			if (!conditionExpression)
				return new Array();

			//prvo vadimo sve pojedinacne izraze, oni su razbijeni ili sa #OR# ili sa #AND#
			var conditionSubexps:Array=conditionExpression.split(ViewOperator.REGEX);
			var conditionOperators:Array=conditionExpression.match(ViewOperator.REGEX);

			//sada imamo niz pojedinacnih izraza, treba da ih rastavimo na objekte
			var conditions:Array=new Array();

			for each (var conditionSubexp:String in conditionSubexps)
			{

				var fields:Array=conditionSubexp.split(ViewFunction.REGEX);

				//svako polje moze biti ili varijabla ili konstanta
				//varijabla pocinje sa @ znakom 
				var leftString:String=fields[0];
				var rightString:String=fields[1];
				//operator
				var functionString:String=conditionSubexp.match(ViewFunction.REGEX)[0];

				var leftField:ViewValue=parseValue(leftString);
				var rightField:ViewValue=parseValue(rightString);
				var func:ViewFunction=new ViewFunction(functionString);

				var conditionOperator:String=conditions.length > 0 ? conditionOperators[conditions.length - 1] : null;
				var condition:ViewCondition=new ViewCondition(leftField, func, rightField, conditionOperator);
				conditions.push(condition);
			}

			return conditions;
		}

		/**
		 * Metoda parsira polje. Polje moze biti varijabla ili konstanta.
		 * Varijabla se definise: @Naziv(s|n|d|b) gde se u zagradi definise tip polja
		 * ako tip polja nije definisan podrazumeva se da je string
		 * Konstanta se definise cistim tekstom ili specijalnim vrednostima.
		 * Specijalne vrednosti su: today, null
		 * @param rightString
		 * @return
		 *
		 */
		private static function parseValue(expression:String):ViewValue
		{

			var value:ViewValue=new ViewValue();

			if (expression.indexOf("@") == 0)
			{

				var dataType:String=ViewValue.STRING;
				//varijabla
				//trazimo oznaku tipa podatka
				var varDataTypes:Array=expression.match(new RegExp("\\([n|d|s|b|o]", "g"));
				if (varDataTypes.length > 0)
					dataType=varDataTypes[0].charAt(1);

				var varValue:String=varDataTypes.length == 0 ? expression.substr(1) : expression.substr(1, expression.length - 4);

				value.dataType=dataType;
				value.value=varValue;
				value.type=ViewValue.TYPE_VARIABLE;
			}
			else if (expression.indexOf("%") == 0)
			{
				//placeholder
				value.type=ViewValue.TYPE_PLACEHOLDER;
				value.value=expression.substr(1);
			}
			else
			{
				//konstanta
				var constDataType:String=ViewValue.STRING;
				//varijabla
				//trazimo oznaku tipa podatka
				var constDataTypes:Array=expression.match(new RegExp("\\([n|d|s|b|o]", "g"));
				if (constDataTypes.length > 0)
					constDataType=constDataTypes[0].charAt(1);

				var constValue:String=constDataTypes.length == 0 ? expression.substr(1) : expression.substr(0, expression.length - 3);
				value.type=ViewValue.TYPE_CONST;
				value.value=constValue;
			}

			return value;
		}

		private static function getToday():Date
		{

			var date:Date=new Date();
			var today:Date=new Date(date.fullYear, date.month, date.date);
			return today;
		}

	}
}


